package com.baidu.iit.pxp.parser;

import com.baidu.iit.pvm.ActivityException;
import com.baidu.iit.pvm.ActivityIllegalArgumentException;
import com.baidu.iit.pvm.process.ActivityImpl;
import com.baidu.iit.pvm.process.ScopeImpl;
import com.baidu.iit.pvm.process.TransitionImpl;
import com.baidu.iit.pxp.BpmnInterfaceImplementation;
import com.baidu.iit.pxp.converter.BpmnXMLConverter;
import com.baidu.iit.pxp.data.ClassStructureDefinition;
import com.baidu.iit.pxp.data.ItemDefinitionData;
import com.baidu.iit.pxp.data.ItemKind;
import com.baidu.iit.pxp.data.StructureDefinition;
import com.baidu.iit.pxp.el.ExpressionManager;
import com.baidu.iit.pxp.entity.ProcessDefinitionEntity;
import com.baidu.iit.pxp.event.BoundaryEvent;
import com.baidu.iit.pxp.event.Event;
import com.baidu.iit.pxp.io.InputStreamSource;
import com.baidu.iit.pxp.io.StreamSource;
import com.baidu.iit.pxp.model.*;
import com.baidu.iit.pxp.model.Process;
import com.baidu.iit.pxp.parser.factory.ActivityBehaviorFactory;
import com.baidu.iit.pxp.parser.factory.ListenerFactory;
import com.baidu.iit.pxp.service.BpmnInterface;
import com.baidu.iit.pxp.service.MessageDefinition;
import com.baidu.iit.pxp.service.OperationImplementation;
import com.baidu.iit.pxp.service.OperationService;
import com.baidu.iit.pxp.util.ReflectUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.util.*;

/**
 * User: huangweili
 * Date: 14-4-24
 * Time: 下午6:59
 */

public class BpmnParse {

    protected static final Logger LOGGER = LoggerFactory.getLogger(BpmnParse.class);

    public static final String PROPERTYNAME_INITIAL = "initial";
    public static final String PROPERTYNAME_CONDITION = "condition";
    public static final String PROPERTYNAME_CONDITION_TEXT = "conditionText";
    public static final String PROPERTYNAME_COMPENSATION_HANDLER_ID = "compensationHandler";
    public static final String PROPERTYNAME_IS_FOR_COMPENSATION = "isForCompensation";
    public static final String PROPERTYNAME_ERROR_EVENT_DEFINITIONS = "errorEventDefinitions";
    public static final String PROPERTYNAME_EVENT_SUBSCRIPTION_DECLARATION = "eventDefinitions";


    protected String name;


    protected StreamSource streamSource;

    protected BpmnModel bpmnModel;

    protected String targetNamespace;


    protected ExpressionManager expressionManager;

    protected List<ProcessDefinitionEntity> processDefinitions = new ArrayList<ProcessDefinitionEntity>();

    protected Map<String, TransitionImpl> sequenceFlows;

    protected BpmnParseHandlers bpmnParserHandlers;

    protected ProcessDefinitionEntity currentProcessDefinition;

    protected FlowElement currentFlowElement;

    protected ActivityImpl currentActivity;

    protected LinkedList<SubProcess> currentSubprocessStack = new LinkedList<SubProcess>();

    protected LinkedList<ScopeImpl> currentScopeStack = new LinkedList<ScopeImpl>();
    protected Map<String, MessageDefinition> messages = new HashMap<String, MessageDefinition>();
    protected Map<String, StructureDefinition> structures = new HashMap<String, StructureDefinition>();
    protected Map<String, BpmnInterfaceImplementation> interfaceImplementations = new HashMap<String, BpmnInterfaceImplementation>();
    protected Map<String, OperationImplementation> operationImplementations = new HashMap<String, OperationImplementation>();
    protected Map<String, ItemDefinitionData> itemDefinitionDatas = new HashMap<String, ItemDefinitionData>();

    protected Map<String, OperationService> operations = new HashMap<String, OperationService>();
    protected Map<String, XMLImporter> importers = new HashMap<String, XMLImporter>();

    protected ActivityBehaviorFactory activityBehaviorFactory;
    protected ListenerFactory listenerFactory;


    public BpmnParse(BpmnParser parser) {
        this.expressionManager = parser.getExpressionManager();
        this.activityBehaviorFactory = parser.getActivityBehaviorFactory();
        this.listenerFactory = parser.getListenerFactory();
        this.bpmnParserHandlers = parser.getBpmnParserHandlers();
        this.initializeXSDItemDefinitions();
    }

    protected void initializeXSDItemDefinitions() {
        this.itemDefinitionDatas.put("http://www.w3.org/2001/XMLSchema:string", new ItemDefinitionData("http://www.w3.org/2001/XMLSchema:string",
                new ClassStructureDefinition(String.class)));
    }


    /**
     * 从xml文件中转成Model对象
     *
     * @return
     */
    public BpmnParse execute() {
        try {
            //xml 转换成对象
            BpmnXMLConverter converter = new BpmnXMLConverter();
            boolean enableSafeBpmnXml = false;
            bpmnModel = converter.convertToBpmnModel(streamSource, true, enableSafeBpmnXml, "UTF-8");

            createImports();
            createItemDefinitions();
            createOperations();
            transformProcessDefinitions();

        } catch (Exception e) {
            if (e instanceof ActivityException) {
                throw (ActivityException) e;
            } else {
                throw new ActivityException("Error parsing XML", e);
            }
        }

        return this;
    }

    public BpmnParse name(String name) {
        this.name = name;
        return this;
    }

    public BpmnParse sourceInputStream(InputStream inputStream) {
        if (name == null) {
            name("inputStream");
        }
        setStreamSource(new InputStreamSource(inputStream));
        return this;
    }


    protected void setStreamSource(StreamSource streamSource) {
        if (this.streamSource != null) {
            throw new ActivityIllegalArgumentException("invalid: multiple sources " + this.streamSource + " and " + streamSource);
        }
        this.streamSource = streamSource;
    }

    protected void createImports() {
        for (Import theImport : bpmnModel.getImports()) {
            XMLImporter importer = this.getImporter(theImport);
            if (importer == null) {
                throw new ActivityException("Could not import item of type " + theImport.getImportType());
            } else {
                importer.importFrom(theImport, this);
            }
        }
    }

    protected XMLImporter getImporter(Import theImport) {
        if (this.importers.containsKey(theImport.getImportType())) {
            return this.importers.get(theImport.getImportType());
        } else {
            if (theImport.getImportType().equals("http://schemas.xmlsoap.org/wsdl/")) {
                Class<?> wsdlImporterClass;
                try {
                    wsdlImporterClass = Class.forName("org.activiti.engine.impl.webservice.CxfWSDLImporter", true, Thread.currentThread().getContextClassLoader());
                    XMLImporter newInstance = (XMLImporter) wsdlImporterClass.newInstance();
                    this.importers.put(theImport.getImportType(), newInstance);
                    return newInstance;
                } catch (Exception e) {
                    throw new ActivityException("Could not find importer for type " + theImport.getImportType());
                }
            }
            return null;
        }
    }


    protected void createItemDefinitions() {
        for (ItemDefinition itemDefinitionElement : bpmnModel.getItemDefinitions().values()) {
            StructureDefinition structure = null;

            try {
                Class<?> classStructure = ReflectUtil.loadClass(itemDefinitionElement.getStructureRef());
                structure = new ClassStructureDefinition(classStructure);
            } catch (ActivityException e) {
                structure = this.structures.get(itemDefinitionElement.getStructureRef());
            }

            ItemDefinitionData itemDefinition = new ItemDefinitionData(itemDefinitionElement.getId(), structure);
            if (StringUtils.isNotEmpty(itemDefinitionElement.getItemKind())) {
                itemDefinition.setItemKind(ItemKind.valueOf(itemDefinitionElement.getItemKind()));
            }
            itemDefinitionDatas.put(itemDefinition.getId(), itemDefinition);
        }
    }

    protected void createOperations() {
        for (Interface interfaceObject : bpmnModel.getInterfaces()) {
            BpmnInterface bpmnInterface = new BpmnInterface(interfaceObject.getId(), interfaceObject.getName());
            bpmnInterface.setImplementation(this.interfaceImplementations.get(interfaceObject.getImplementationRef()));

            for (Operation operationObject : interfaceObject.getOperations()) {
                if (this.messages.containsKey(operationObject.getInMessageRef())) {
                    MessageDefinition inMessage = this.messages.get(operationObject.getInMessageRef());
                    OperationService operation = new OperationService(operationObject.getId(), operationObject.getName(), bpmnInterface, inMessage);
                    operation.setImplementation(this.operationImplementations.get(operationObject.getImplementationRef()));

                    if (StringUtils.isNotEmpty(operationObject.getOutMessageRef())) {
                        if (this.messages.containsKey(operationObject.getOutMessageRef())) {
                            MessageDefinition outMessage = this.messages.get(operationObject.getOutMessageRef());
                            operation.setOutMessage(outMessage);
                        }
                    }

                    operations.put(operation.getId(), operation);
                }
            }
        }
    }

    protected void transformProcessDefinitions() {
        sequenceFlows = new HashMap<String, TransitionImpl>();
        for (Process process : bpmnModel.getProcesses()) {
                bpmnParserHandlers.parseElement(this, process);
        }
    }

    public void processFlowElements(Collection<FlowElement> flowElements) {

        List<SequenceFlow> sequenceFlowToParse = new ArrayList<SequenceFlow>();
        List<BoundaryEvent> boundaryEventsToParse = new ArrayList<BoundaryEvent>();

        List<FlowElement> defferedFlowElementsToParse = new ArrayList<FlowElement>();

        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof SequenceFlow) {
                sequenceFlowToParse.add((SequenceFlow) flowElement);
            } else if (flowElement instanceof BoundaryEvent) {
                boundaryEventsToParse.add((BoundaryEvent) flowElement);
            } else if (flowElement instanceof Event) {
                defferedFlowElementsToParse.add(flowElement);
            } else {
                bpmnParserHandlers.parseElement(this, flowElement);
            }

        }

        for (FlowElement flowElement : defferedFlowElementsToParse) {
            bpmnParserHandlers.parseElement(this, flowElement);
        }

        for (BoundaryEvent boundaryEvent : boundaryEventsToParse) {
            bpmnParserHandlers.parseElement(this, boundaryEvent);
        }

        for (SequenceFlow sequenceFlow : sequenceFlowToParse) {
            bpmnParserHandlers.parseElement(this, sequenceFlow);
        }

    }


    public ProcessDefinitionEntity getProcessDefinition(String processDefinitionKey) {
        for (ProcessDefinitionEntity processDefinition : processDefinitions) {
            if (processDefinition.getKey().equals(processDefinitionKey)) {
                return processDefinition;
            }
        }
        return null;
    }

    public List<ProcessDefinitionEntity> getProcessDefinitions() {
        return processDefinitions;
    }

    public String getTargetNamespace() {
        return targetNamespace;
    }

    public BpmnParseHandlers getBpmnParserHandlers() {
        return bpmnParserHandlers;
    }

    public void setBpmnParserHandlers(BpmnParseHandlers bpmnParserHandlers) {
        this.bpmnParserHandlers = bpmnParserHandlers;
    }


    public BpmnModel getBpmnModel() {
        return bpmnModel;
    }

    public void setBpmnModel(BpmnModel bpmnModel) {
        this.bpmnModel = bpmnModel;
    }

    public ActivityBehaviorFactory getActivityBehaviorFactory() {
        return activityBehaviorFactory;
    }

    public void setActivityBehaviorFactory(ActivityBehaviorFactory activityBehaviorFactory) {
        this.activityBehaviorFactory = activityBehaviorFactory;
    }

    public ListenerFactory getListenerFactory() {
        return listenerFactory;
    }

    public void setListenerFactory(ListenerFactory listenerFactory) {
        this.listenerFactory = listenerFactory;
    }


    public ExpressionManager getExpressionManager() {
        return expressionManager;
    }


    public void setExpressionManager(ExpressionManager expressionManager) {
        this.expressionManager = expressionManager;
    }

    public Map<String, TransitionImpl> getSequenceFlows() {
        return sequenceFlows;
    }

    public Map<String, MessageDefinition> getMessages() {
        return messages;
    }

    public Map<String, BpmnInterfaceImplementation> getInterfaceImplementations() {
        return interfaceImplementations;
    }

    public Map<String, ItemDefinitionData> getItemDefinitions() {
        return itemDefinitionDatas;
    }

    public Map<String, XMLImporter> getImporters() {
        return importers;
    }

    public Map<String, OperationService> getOperations() {
        return operations;
    }

    public void setOperations(Map<String, OperationService> operations) {
        this.operations = operations;
    }

    public ProcessDefinitionEntity getCurrentProcessDefinition() {
        return currentProcessDefinition;
    }

    public void setCurrentProcessDefinition(ProcessDefinitionEntity currentProcessDefinition) {
        this.currentProcessDefinition = currentProcessDefinition;
    }

    public FlowElement getCurrentFlowElement() {
        return currentFlowElement;
    }

    public void setCurrentFlowElement(FlowElement currentFlowElement) {
        this.currentFlowElement = currentFlowElement;
    }

    public ActivityImpl getCurrentActivity() {
        return currentActivity;
    }

    public void setCurrentActivity(ActivityImpl currentActivity) {
        this.currentActivity = currentActivity;
    }

    public void setCurrentSubProcess(SubProcess subProcess) {
        currentSubprocessStack.push(subProcess);
    }

    public SubProcess getCurrentSubProcess() {
        return currentSubprocessStack.peek();
    }

    public void removeCurrentSubProcess() {
        currentSubprocessStack.pop();
    }

    public void setCurrentScope(ScopeImpl scope) {
        currentScopeStack.push(scope);
    }

    public ScopeImpl getCurrentScope() {
        return currentScopeStack.peek();
    }

    public void removeCurrentScope() {
        currentScopeStack.pop();
    }

}